module protocol {
export class Protocol {
    
    public static flags:Array<number> = [1, 2, 4, 8, 16, 32, 64, 128];

    public protocolID:any;
    public __param:ProtocolParam;

    public constructor() {
        
    }

    public init(...args:Array<any>):Protocol {
        if (this.__param.fieldList.length != args.length) {
            throw new Error("Protocol init error:number of params does not match!");
        }
        var l:number = this.__param.fieldList.length;
        for (var i = 0; i < l; i++) {
            this.setData(this.__param.fieldList[i].name, args[i]);
        }
        return this;
    }

    public merge(value:Protocol):void {
        if (value == null || value.__param != this.__param) {
            return;
        }
        for (var field of this.__param.fieldList) {
            var name = field.name;
            if (!field.isOptional() || value.getChanged(name)) {
                if (!field.isRepeated() && field.isCustom()) {
                    var custom:Protocol = this[name];
                    if (!custom) {
                        custom = Protocols.createProtocol(field.fieldType);
                        this.setData(name, custom);
                    }
                    custom.merge(value[name]);
                } else {
                    this.setData(name, value[name]);
                }
            }
        }
    }

    public reset():void {
        for (var field of this.__param.fieldList) {
            delete this[field.name];
            if (field.isOptional()) {
                delete this[field.name + "_changed"];
            }
        }
    }

    public encode(bytes:egret.ByteArray):void {
        var i:number;
        var l:number;
        var changeFlags:Array<number>;
        l = this.__param.changeFlagsLength;
        if (l > 0) {
            changeFlags = [];
            for (i = 0; i < l; i++) {
                changeFlags[i] = 0;
            }
            l = this.__param.changeFieldList.length;
            for (i = 0; i < l; i++) {
                if (this.getChanged(this.__param.changeFieldList[i].name)) {
                    var flagIndex:number = i >> 3;
                    var flagNum:number = i % 8;
                    changeFlags[flagIndex] |= Protocol.flags[flagNum];
                }
            }
            l = this.__param.changeFlagsLength;
            for (i = 0; i < l; i++) {
                bytes.writeByte(changeFlags[i]);
            }
        }
        l = this.__param.fieldList.length;
        for (i = 0; i < l; i++) {
            var field:ProtocolField = this.__param.fieldList[i];
            var name = field.name;
            if (!field.isOptional() || this.getChanged(name)) {
                var data:any = this[name];
                if (field.isRepeated()) {
                    if (data != null) {
                        var dataLength:number = data.length;
                        bytes.writeShort(dataLength);
                        for (var j = 0; j < dataLength; j++) {
                            this.writeData(bytes, data[j], field.fieldType);
                        }
                    } else {
                        bytes.writeShort(0);
                    }
                } else {
                    this.writeData(bytes, data, field.fieldType);
                }
            }
        }
    }

    public decode(bytes:egret.ByteArray):void {
        var i:number;
        var l:number;
        var changeFlags:Array<number>;
        l = this.__param.changeFlagsLength;
        if (l > 0) {
            changeFlags = [];
            for (i = 0; i < l; i++) {
                changeFlags[i] = bytes.readByte();
            }
        }
        var flagCount:number = 0;
        l = this.__param.fieldList.length;
        for (i = 0; i < l; i++) {
            var field:ProtocolField = this.__param.fieldList[i];
            var name = field.name;
            if (field.isOptional()) {
                var flagIndex:number = flagCount >> 3;
                var flagNum:number = flagCount % 8;
                if ((changeFlags[flagIndex] & Protocol.flags[flagNum]) != 0) {
                    if (field.isRepeated()) {
                        var dataLength:number = bytes.readShort();
                        var array:Array<any> = [];
                        for (var j = 0; j < dataLength; j++) {
                            array[j] = this.readData(bytes, field.fieldType);
                        }
                        this.setData(name, array);
                    } else {
                        this.setData(name, this.readData(bytes, field.fieldType));
                    }
                }
                flagCount++;
            } else {
                this.setData(name, this.readData(bytes, field.fieldType));
            }
        }
    }

    private writeData(bytes:egret.ByteArray, data:any, fieldType:ProtocolFieldType):void {
        // TS编译时枚举访问会变为ProtocolFieldType["name"]，为性能考虑改为直接判断数字
        switch (fieldType) {
            case 0:
                bytes.writeByte(data);
                break;
            case 1:
                bytes.writeBoolean(data);
                break;
            case 2:
                bytes.writeInt(data);
                break;
            case 3:
                bytes.writeUnsignedInt(data);
                break;
            case 4:
                bytes.writeShort(data);
                break;
            case 5:
                bytes.writeUnsignedShort(data);
                break;
            case 6:
                bytes.writeFloat(data);
                break;
            case 7:
                bytes.writeDouble(data);
                break;
            case 8:
                bytes.writeUTF(data);
                break;
            case 9:
                bytes.writeUnsignedShort(data.length);
                bytes.writeBytes(data);
                break;
            default:
                data.encode(bytes);
                break;
        }
    }

    private readData(bytes:egret.ByteArray, fieldType:ProtocolFieldType):any {
        // TS编译时枚举访问会变为ProtocolFieldType["name"]，为性能考虑改为直接判断数字
        switch (fieldType) {
            case 0:
                return bytes.readByte();
            case 1:
                return bytes.readBoolean();
            case 2:
                return bytes.readInt();
            case 3:
                return bytes.readUnsignedInt();
            case 4:
                return bytes.readShort();
            case 5:
                return bytes.readUnsignedShort();
            case 6:
                return bytes.readFloat();
            case 7:
                return bytes.readDouble();
            case 8:
                return bytes.readUTF();
            case 9:
                var bytesData:egret.ByteArray = new egret.ByteArray();
                bytes.readBytes(bytesData, 0, bytes.readUnsignedShort());
                return bytesData;
            default:
                var protocol:Protocol = Protocols.createProtocol(fieldType);
                protocol.decode(bytes);
                return protocol;
        }
    }

    /**
     * 设置数据
     */
    public setData(name:string, value:any):void {
        var field:ProtocolField = this.__param.fieldDict[name];
        if (field != null) {
            this[name] = value;
            if (field.isOptional()) {
                this[name + "_changed"] = true;
            }
        } else {
            throw new Error("Protocol setData error:can not find field:" + name);
        }
    }

    /**
     * 获取数据
     */
    public getData(name:string):any {
        return this[name];
    }

    /**
     * 获取数据是否改变
     */
    public getChanged(name:string):boolean {
        return this.hasOwnProperty(name);
    }
}
}